251109 用Godot实现的行为树

简介

行为树是一种用来表达与实现复杂行为的工具,它具有以下特征:

  1. 行为树一定得是树:由节点和边构建,节点之间不存在闭环;假设下图的虚线真的连上了,它就不再是树了。
  2. 叶子节点代表可执行的行为:叶子节点是没有子节点的节点,是根节点之外的只有一条边的节点;它代表一个具体的行为,执行该行为后需要返回执行状态(成功、失败、运行中……)
  3. 非叶子节点控制树的遍历:内部节点根据子节点返回的状态,根据一定规则确定下一个执行的节点,返回自己的执行状态。

行为树

在游戏中,我们常用行为树制作NPC的AI,自机角色的控制也可通过行为树构建。

概念

流程

行为树执行一个节点可能返回三种基本状态:成功、失败和运行中。当前没有运行中的节点时,行为树的执行是从根节点开始的,根据一定规则遍历树中的节点,直到遇到运行中的节点;当前存在运行中的节点时,行为树就直接执行该节点,直到它执行结束。

此外,行为树可能出现运行中的节点被打断的操作。

想象一下这样的场景:一个太刀侠打出了登龙(气刃兜割),正在半空中迅速下坠,直到落地都无法做出其它任何行动;与此同时,另外一边,怪物使用了一个强而有力的技能击杀了太刀侠。

正常情况下,太刀侠应该会立刻停止登龙,进入战败CG;但如果这个行为树没考虑打断,太刀侠还会继续登龙直到落地,很壮烈但毕竟不合常理。

因此,打断往往也是设计行为树需要考虑的点。

节点

行为树有以下几种基本节点:

此外,还有一些节点可能会为构建行为树带来便利:

图例

实现

最近笔者正在探索Godot,发现屏幕左侧的场景树居然是树状结构,树中的节点还能不包含空间信息;右侧的检查器窗口与树结构分离开,用于编辑数据十分清晰简洁,天然适合作为行为树的图形化编辑界面,故使用 Godot 4.4.1 Mono版本实现行为树。

节点的基类

using Godot;

namespace BehaviorTree;

[GlobalClass]
public abstract partial class BTNode : Node
{
    public State CurrentState { get; private set; }

    public enum State
    {
        Aborted,
        Running,
        Success,
        Failure,
    }

    public void Tick(double delta)
    {
        if (CurrentState != State.Running) OnEnter();
        CurrentState = Process(delta);
        if (CurrentState != State.Running) OnExit();
    }

    public virtual bool Abort(AbortInfo info)
    {
        CurrentState = State.Aborted;
        return true;
    }

    public virtual void Input(InputEvent input) { }

    protected abstract State Process(double delta);

    public virtual void Init() { }

    protected virtual void OnEnter() { }

    protected virtual void OnExit() { }
}

选择节点

using Godot;

namespace BehaviorTree;

[GlobalClass]
public partial class BTSelectNode : BTCompositeNode
{
    protected override State Process(double delta)
    {
        int idx = 0;
        for (int i = 0; i < BTChildren.Length; i++)
            if (BTChildren[i].CurrentState == State.Running)
            {
                idx = i;
                break;
            }

        for (int i = idx; i < BTChildren.Length; i++)
        {
            var node = BTChildren[i];
            node.Tick(delta);
            if (node.CurrentState != State.Failure) return node.CurrentState;
        }

        return State.Failure;
    }
}

序列节点

using Godot;

namespace BehaviorTree;

[GlobalClass]
public partial class BTSequenceNode : BTCompositeNode
{
    protected override State Process(double delta)
    {
        int idx = 0;
        for (int i = 0; i < BTChildren.Length; i++)
            if (BTChildren[i].CurrentState == State.Running)
            {
                idx = i;
                break;
            }

        for (int i = idx; i < BTChildren.Length; i++)
        {
            var node = BTChildren[i];
            node.Tick(delta);
            if (node.CurrentState != State.Success) return node.CurrentState;
        }

        return State.Success;
    }
}

装饰节点基类

using Godot;

namespace BehaviorTree;

[GlobalClass]
public abstract partial class BTDecorator : BTNode
{
    protected BTNode BTChild { get; private set; }

    public override void Init()
    {
        base.Init();

        BTChild = GetChild<BTNode>(0);
    }

    public override bool Abort(AbortInfo info)
    {
        if (BTChild.CurrentState == State.Running && BTChild.Abort(info)) return base.Abort(info);
        return false;
    }
}

条件节点基类

using Godot;

namespace BehaviorTree;

[GlobalClass]
public abstract partial class BTConditionNode : BTNode
{
    protected abstract bool Meets();

    protected sealed override State Process(double delta) => Meets() ? State.Success : State.Failure;
}

样例

一个小怪有如下行为:

它的行为可以描述成下图:

行为树1

在Godot场景树中可以这样表示(存在用于打log的冗余节点,但大致结构相同):

场景树1

运行起来看看效果:

效果1

一个回合制游戏中的小怪,拥有以下行为模式:

它的行为可以描述成下图:

行为树2

这棵树仅在轮到这只怪物行动时执行一次

在Godot场景树中的结构见实现部分第张图

预览如下:

效果2

拓展

构建游戏AI的方法不止行为树一种,此前我们常用的方法是有限状态机,还有HTN、GOAP等方法可以了解与探索,感兴趣的话可以看看 GAMES104讲解游戏AI决策的部分:

  1. 16.游戏引擎Gameplay玩法系统:基础AI (Part 2) | GAMES104-现代游戏引擎:从入门到实践:有限状态机、行为树
  2. 17.游戏引擎Gameplay玩法系统:高级AI (Part 1) | GAMES104-现代游戏引擎:从入门到实践:HTN、GOAP、MCTS

参考

概念:https://zhuanlan.zhihu.com/p/463182588

实现:https://github.com/CloverJoyi/KDQS/tree/main/Assets/BehaviorTree

样例:https://github.com/SubstanceP114/bt-test